package com.jerome.fs.util;

import com.jerome.fs.enums.ClassEnum;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

/**
 * Utility class for reflection operations.
 * Provides methods for working with reflection, including extracting getter and setter methods
 * and converting objects and maps into specified entity types.
 * Supports basic data types and Date conversions.
 *
 * @author Jerome
 * @date 2023/9/9
 */
public class ReflectUtil {
    /**
     * Get property names and their corresponding getter methods for a given class.
     *
     * @param clazz The class to analyze.
     * @return A map containing property names and their corresponding getter methods.
     */
    public static Map<String, Method> getGetter(Class clazz) {
        Map<String, Method> map = new HashMap<>(16);
        Method[] methods = clazz.getDeclaredMethods();
        String name;
        Class<?>[] parameterTypes;
        for (Method method : methods) {
            name = method.getName();
            parameterTypes = method.getParameterTypes();
            if (name.startsWith("get") && parameterTypes.length == 0 && name.length() > 3) {
                if (name.length() == 4) {
                    map.put(name.substring(3, 4).toLowerCase(), method);
                } else {
                    map.put(name.substring(3, 4).toLowerCase() + name.substring(4), method);
                }
            }
        }
        return map;
    }

    /**
     * Get property names and their corresponding setter methods for a given class.
     *
     * @param clazz The class to analyze.
     * @return A map containing property names and their corresponding setter methods.
     */
    public static Map<String, Method> getSetter(Class clazz) {
        Map<String, Method> map = new HashMap<>(16);
        Method[] methods = clazz.getDeclaredMethods();
        String name;
        Class<?>[] parameterTypes;
        for (Method method : methods) {
            name = method.getName();
            parameterTypes = method.getParameterTypes();
            if (name.startsWith("set") && parameterTypes.length == 1 && name.length() > 3) {
                if (name.length() == 4) {
                    map.put(name.substring(3, 4).toLowerCase(), method);
                } else {
                    map.put(name.substring(3, 4).toLowerCase() + name.substring(4), method);
                }
            }
        }
        return map;
    }

    /**
     * Convert a bean object to an object of type T.
     *
     * @param bean        The source bean object.
     * @param entityClazz The target entity class.
     * @param <T>         The target entity type.
     * @return An object of type T.
     */
    public static <T> T build(Object bean, Class<T> entityClazz) throws IllegalAccessException, InstantiationException {
        if (bean instanceof Map) {
            return build((Map<Object, Object>) bean, entityClazz);
        }
        // Get the setter methods of the target object
        Map<String, Method> setterMap = ReflectUtil.getSetter(entityClazz);
        Class<?> cls = bean.getClass();
        T t = entityClazz.newInstance();
        Field[] fields = cls.getDeclaredFields();
        for (Field f : fields) {
            f.setAccessible(true);
            Object value = f.get(bean);
            if (value == null) {
                continue;
            }
            Method method = setterMap.get(f.getName());
            if (method != null) {
                try {
                    method.invoke(t, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return t;
    }

    /**
     * Convert a map to an object of type T.
     *
     * @param map         The source map.
     * @param entityClazz The target entity class.
     * @param <T>         The target entity type.
     * @return An object of type T.
     */
    private static <T> T build(Map<Object, Object> map, Class<T> entityClazz) throws IllegalAccessException, InstantiationException {
        Map<String, Method> setterMap = ReflectUtil.getSetter(entityClazz);
        T t = entityClazz.newInstance();
        for (Map.Entry<Object, Object> entry : map.entrySet()) {
            Object value = entry.getValue();
            if (value == null) {
                continue;
            }
            Method method = setterMap.get(entry.getKey().toString());
            if (method != null) {
                try {
                    setValue(t, value, method);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return t;
    }

    /**
     * Set the value of a field in the target object.
     * Currently, only basic data types and Date are supported.
     *
     * @param t      The target object.
     * @param value  The value to set.
     * @param method The setter method to use.
     * @param <T>    The generic type.
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private static <T> void setValue(T t, Object value, Method method) throws IllegalAccessException, InvocationTargetException, ParseException {
        Class<?>[] parameterTypes = method.getParameterTypes();
        ClassEnum clazz = ClassEnum.getByClazz(parameterTypes[0]);
        if (clazz == null) {
            throw new IllegalAccessException(parameterTypes[0] + " is not configured in ClassEnum");
        }
        switch (clazz) {
            case String:
                method.invoke(t, value.toString());
                break;
            case Character:
            case CHAR:
                method.invoke(t, value.toString().charAt(0));
                break;
            case Byte:
            case BYTE:
                method.invoke(t, Byte.valueOf(value.toString()));
                break;
            case Short:
            case SHORT:
                method.invoke(t, Short.valueOf(value.toString()));
                break;
            case Integer:
            case INT:
                method.invoke(t, Integer.valueOf(value.toString()));
                break;
            case Long:
            case LONG:
                method.invoke(t, Long.valueOf(value.toString()));
                break;
            case Boolean:
            case BOOLEAN:
                method.invoke(t, Boolean.valueOf(value.toString()));
                break;
            case Float:
            case FLOAT:
                method.invoke(t, Float.valueOf(value.toString()));
                break;
            case Double:
            case DOUBLE:
                method.invoke(t, Double.valueOf(value.toString()));
                break;
            case BigDecimal:
                method.invoke(t, new BigDecimal(value.toString()));
                break;
            case Date:
                method.invoke(t, DateUtil.parseDate(value.toString()));
                break;
            default:
                throw new IllegalAccessException(parameterTypes[0] + " is not configured in ClassEnum");
        }
    }
}
